/* libfontman.c: TeX Font Manager library */

#include "libfontman.h"
#include "internal.h"

fontman_list_t list_context;

#define NR_ELEMS(arr)   (sizeof(arr) / sizeof(arr[0]))

const char *fontman_encoding_list[] = {
    "UTF-16BE",                     /* FONTMAN_ENC_UTF16BE */
    "GBK",                          /* FONTMAN_ENC_GBK     */
    "UTF-16LE",                     /* FONTMAN_ENC_UTF16LE */
    "BIG-5",                        /* FONTMAN_ENC_BIG5    */
};

/* conversion descriptor list */
iconv_t fontman_cd_list[] = {
    (iconv_t) -1,                   /* FONTMAN_ENC_UTF16BE */
    (iconv_t) -1,                   /* FONTMAN_ENC_GBK     */
    (iconv_t) -1,                   /* FONTMAN_ENC_UTF16LE */
    (iconv_t) -1,                   /* FONTMAN_ENC_BIG5    */
};

/* a sorted combination to try when detecting local font names,  */
struct fontman_sfnt_name sfnt_name_combination[] = {
    { TT_PLATFORM_MICROSOFT,    TT_MS_ID_UNICODE_CS, 
      TT_MS_LANGID_CHINESE_PRC, TT_NAME_ID_FULL_NAME, 
      FONTMAN_ENC_UTF16BE }, 
    
    { TT_PLATFORM_MACINTOSH,    TT_MAC_ID_SIMPLIFIED_CHINESE, 
      TT_MAC_LANGID_CHINESE_SIMPLIFIED, TT_NAME_ID_FULL_NAME, 
      FONTMAN_ENC_GBK }, 
    
    { TT_PLATFORM_MACINTOSH,    TT_MAC_ID_TRADITIONAL_CHINESE, 
      TT_MAC_LANGID_CHINESE_TRADITIONAL, TT_NAME_ID_FULL_NAME, 
      FONTMAN_ENC_BIG5 }, 

    { FONTMAN_SFNT_END, FONTMAN_SFNT_END, 
      FONTMAN_SFNT_END, FONTMAN_SFNT_END, 0 }
};

int fontman_list_init(enum fontman_list_type type, 
                      const char *path, 
                      const char *pattern, 
                      unsigned int flags)
{
    FT_Error error;
    list_context.type = type;
    list_context.pattern = pattern;
    list_context.path = path;
    list_context.flags = flags;

    error = FT_Init_FreeType(&list_context.lib);
    if (error)
        return FONTMAN_ERR_INIT_FREETYPE;

    if (type == FONTMAN_LIST_PATH)
    {
        int ret;

        ret = fontman_list_dir_init(path);
        if (ret)
            return ret;

        return 0;
    }
    else
        return fontman_list_platform_init();
}

int fontman_list_next(fontman_face_t *face)
{
    int err = 0;
    unsigned int flags = list_context.flags;

    /* first we clean everything up to make sure */
    face->name[0]       = '\0';
    face->ps_name[0]    = '\0';
    face->local_name[0] = '\0';
    face->path[0]       = '\0';

    if (list_context.type == FONTMAN_LIST_PATH)
    {
        err = fontman_list_dir_next(face->path, PATH_MAX - 1);
        if (err)
            return err;

        err = fontman_get_font_info(&list_context.lib, 
                                    face, flags);
        if (err)
            return err;
    }
    else
    {
        err = fontman_list_platform_next(face);
        if (err)
            return err;
    }

    /* check patterns */
    if (! fontman_list_pattern_matched(face->name))
        return fontman_list_next(face);

    return 0;
}

void fontman_list_free()
{
    int i;

    FT_Done_FreeType(list_context.lib);
    if (list_context.type == FONTMAN_LIST_PATH)
        fontman_list_dir_free();
    else
        fontman_list_platform_free();

    for (i = 0; i < NR_ELEMS(fontman_cd_list); i++)
        if (fontman_cd_list[i] != (iconv_t) -1)
            iconv_close(fontman_cd_list[i]);
}

/* if id1 == NULL, mean we can just ignore that field */
#define check_the_same(id1, id2)    (id2 == FONTMAN_SFNT_ALL || id1 == id2)

/* we already have path setted, now need to get other info with 
 * FreeType library */
int fontman_get_font_info(FT_Library *lib, 
                          fontman_face_t *face, 
                          unsigned int flags)
{
    FT_Error    error;
    FT_Face     ft_face;
    const char *str;
    int         ret = 0;

    error = FT_New_Face(*lib, face->path, 0, &ft_face);
    if (error)
        return FONTMAN_ERR_OPEN_FTFACE;

    if (flags & FONTMAN_GET_NAME)
    {
        strncpy(face->name, ft_face->family_name, 
                FONTMAN_MAX_FONT_NAME);

        if (ft_face->style_name != NULL)
        {
            strcat(face->name, " ");
            strcat(face->name, ft_face->style_name);
        }
    }

    if (flags & FONTMAN_GET_PS_NAME)
    {
        str = FT_Get_Postscript_Name(ft_face);
        if (str == NULL)
            ret = FONTMAN_ERR_GET_PS_NAME;

        strncpy(face->ps_name, str, FONTMAN_MAX_PS_NAME);
    }

    if (flags & FONTMAN_GET_LOCAL_NAME)
    {
        FT_SfntName name;
        struct fontman_sfnt_name *item;
        int i, j, count;

        count = FT_Get_Sfnt_Name_Count(ft_face);
        face->local_name[0] = '\0';

        /* If there is no sfnt name table entry, just clean up
         * the local_name field in fontman_face_t struct to 
         * indicate we can not find one */
        if (count <= 0)
            goto done_get_name;

        for (i = 0; i < count; i++)
        {
            error = FT_Get_Sfnt_Name(ft_face, i, &name);
            if (error)
                continue;

            /* we try combinations in our predefined list one by one, 
             * when all id matched, we can call the convert function 
             * defined in fontman_sfnt_name structure to do encoding 
             * conversion, if the conversion succeed (return 0), we 
             * can just out of the loop at once, since we only need 
             * one local font name */
            for (j = 0; 
                 item = &sfnt_name_combination[j], 
                 item->platform_id != FONTMAN_SFNT_END; j++)
            {
                if (check_the_same(name.platform_id, item->platform_id) &&
                    check_the_same(name.encoding_id, item->encoding_id) &&
                    check_the_same(name.language_id, item->language_id) &&
                    check_the_same(name.name_id,     item->name_id))
                    if (convert_sfnt_name(face->local_name, 
                                          (const char *) name.string, 
                                          name.string_len, 
                                          item->enc_id) == 0)
                        goto done_get_name;
            }
        }
    }

done_get_name:
    if (flags & FONTMAN_GET_EXTRA_INFO)
    {
        fontman_info_t *info;
        int i;

        if (face->extra == NULL)
            face->extra = (fontman_info_t *) 
                malloc(sizeof(fontman_info_t));

        info = face->extra;
        info->num_glyphs = ft_face->num_glyphs;
        info->num_charmaps = ft_face->num_charmaps;

        strcpy(info->family, ft_face->family_name);
        strcpy(info->style, ft_face->style_name);

        init_info_charmaps(info);

        for (i = 0; i < info->num_charmaps; i++)
        {
            info->charmaps[i]->pid = ft_face->charmaps[i]->platform_id;
            info->charmaps[i]->eid = ft_face->charmaps[i]->encoding_id;
        }
    }

    FT_Done_Face(ft_face);
    return 0;
}

int fontman_list_pattern_matched(const char *str)
{
    return (list_context.pattern == NULL || 
            strcasestr(str, list_context.pattern) != NULL);
}

int convert_sfnt_name(char *dest_name, 
                      const char *sfnt_name, 
                      int sfnt_name_len, 
                      enum fontman_encoding enc_id)
{
    const char *source;
    char *target;
    size_t src_len, dest_len;
    iconv_t *cdp;

    cdp = &fontman_cd_list[enc_id];
    /* try to use a cached convert descriptor, do not open one
     * each time we trying to do conversion */
    if (*cdp == (iconv_t) -1)
        *cdp = iconv_open("UTF-8", fontman_encoding_list[enc_id]);

    source   = sfnt_name;
    target   = dest_name;
    src_len  = sfnt_name_len;
    dest_len = FONTMAN_MAX_LOCAL_NAME;

    if (iconv(*cdp, &source, &src_len, &target, &dest_len) < 0)
        return 1;

    *target = '\0';
    return 0;
}

const char *fontman_error_msg_list[] = {
    "initialize platform-dependent library failed", 
    "open specified path failed", 
    "no next font",
    "get font path failed", 
    "get font name failed", 
    "get font PostScript name failed", 
    "initialize FreeType library failed", 
    "open FreeType font face failed", 
    "this method is not supported", 
    "can not find font", 
};

const char *fontman_error_msg(int error)
{
    if (error < FONTMAN_ERR_MAX && error >= 1)
        return fontman_error_msg_list[error - 1];

    return "unknown error";
}

int fontman_info(fontman_face_t *face, 
                 enum fontman_info_type type)
{
    face->extra = NULL;

    switch (type)
    {
    case FONTMAN_INFO_SYSTEM:
        return fontman_info_platform_font(face);

    case FONTMAN_INFO_FILE:
        return fontman_info_file(face, 
                                 FONTMAN_GET_ALL);

    default:
        /* other type is not supported yet */
        return FONTMAN_ERR_NOT_SUPPORTED;
    }
}

void free_info_charmaps(fontman_info_t *info)
{
    int i;

    for (i = 0; i < info->num_charmaps; i++)
        free(info->charmaps[i]);

    free(info->charmaps);
}

void fontman_info_done(fontman_face_t *face)
{
    free_info_charmaps(face->extra);

    free(face->extra);
}

int fontman_info_file(fontman_face_t *face, unsigned int flags)
{
    FT_Library lib;
    int err;
    FT_Error error;

    error = FT_Init_FreeType(&lib);
    if (error)
        return FONTMAN_ERR_INIT_FREETYPE;

    err = fontman_get_font_info(&lib, face, flags);

    FT_Done_FreeType(lib);
    return err;
}

void init_info_charmaps(fontman_info_t *info)
{
    int i;

    info->charmaps = (fontman_charmap_t **) 
        malloc(info->num_charmaps * 
               sizeof(fontman_charmap_t *));

    for (i = 0; i < info->num_charmaps; i++)
        info->charmaps[i] = (fontman_charmap_t *) 
            malloc(sizeof(fontman_charmap_t));
}

const char *fontman_platform_list[] = {
    "Unicode (Apple)",      /* 0 */
    "Macintosh",            /* 1 */
    "ISO (deprecated)",     /* 2 */
    "Microsoft",            /* 3 */
    "Custom",               /* 4 */
    NULL,                   /* 5 */
    NULL,                   /* 6 */
    "Adobe",                /* 7 */
};

const char *fontman_get_platform(unsigned short pid)
{
    if (pid <= FONTMAN_MAX_PLATFORM_ID && 
        fontman_platform_list[pid] != NULL)
        return fontman_platform_list[pid];

    return "Unknown platform";
}

const char *fontman_apple_encoding_list[] = {
    "Default (Unicode 1.0)", 
    "Unicode 1.1", 
    "ISO 10646 (deprecated)", 
    "Unicode 2.0", 
    "Full Unicode"
};

const char *fontman_get_apple_encoding(unsigned short eid)
{
    if (eid <= FONTMAN_MAX_APPLE_ENCODING)
        return fontman_apple_encoding_list[eid];

    return "Unknown encoding";
}

const char *fontman_ms_encoding_list[] = {
    "Symbol",           /* 0 */
    "Unicode (WGL4)",   /* 1 */
    "Shift JIS",        /* 2 */
    "GB2312",           /* 3 */
    "Big5",             /* 4 */
    "Wansung",          /* 5 */
    "Johab",            /* 6 */
    NULL,               /* 7 */
    NULL,               /* 8 */
    NULL,               /* 9 */
    "UCS4"              /* 10 */
};

const char *fontman_get_ms_encoding(unsigned short eid)
{
    if (eid <= FONTMAN_MAX_MS_ENCODING && 
        fontman_ms_encoding_list[eid] != NULL)
        return fontman_ms_encoding_list[eid];

    return "Unknown encoding";
}

const char *fontman_get_backend()
{
    return FONTMAN_BACKEND;
}



